package life;

import java.util.ArrayList;
import java.util.Properties;

public class Liso
{

	double energy = 200;
	Coordinate position;
	Coordinate vel; // just the direction NOT the vector
	double age = 0;
	// double radiusPeception;// radius of the perception
	// double perception;// probability to spot something inside the radius
	// // Perception
	// double lisoLifeSpan;
	// double fertility;
	int generation ;
	Properties prop = new Properties();

	public static String ENERGY_CONSUME = "ENERGY_CONSUME";
	public static String PERCEPTION_RADIUS = "PERCEPTION_RADIUS";
	public static String PERCEPTION = "PERCEPTION";
	public static String LIFE_SPAN = "LIFE_SPAN";
	public static String FERTILITY = "FERTILITY";

	public Liso(World world, double lisoLifeSpan, int generation)
	{
		prop.setProperty(PERCEPTION_RADIUS, "70.0");
		prop.setProperty(PERCEPTION, "0.6");
		prop.setProperty(LIFE_SPAN, lisoLifeSpan + "");
		prop.setProperty(FERTILITY, "0.2");
		prop.setProperty(ENERGY_CONSUME, "2.0");
		this.position = new Coordinate(Coordinate.RANDOM, world);
		this.vel = new Coordinate(Coordinate.VELOCITY, 1);
		this.generation = generation;

	}

	public Liso(World w, double lisoLifeSpan, int newGen, Properties babyProp)
	{
		this.position = new Coordinate(Coordinate.RANDOM, w);
		this.vel = new Coordinate(Coordinate.VELOCITY, 1);
		this.generation = newGen;
		this.prop=babyProp;
	}

	public Liso()
	{
	}

	public void aged()
	{
		age++;
		energy = energy - dailyEnergyConsume();
		// calucalate fertility
	}

	private double dailyEnergyConsume()
	{
		return Double.parseDouble(prop.getProperty(ENERGY_CONSUME));
	}

	public boolean isStillAlive()
	{
		if (age < lisoLifeSpan() && hasEnergy())
			return true;

		double deathProbability = 1.0 - 1.0 / (age - lisoLifeSpan() + 1); // 1+1/(x+1)

		if (Math.random() > deathProbability && hasEnergy())
			return true;

		return false;

	}

	private double lisoLifeSpan()
	{
		return Double.parseDouble(prop.getProperty(LIFE_SPAN));
	}

	private boolean hasEnergy()
	{
		return energy > 0;
	}

	public void move(World world)
	{
		position.x = position.x + vel.x * getVelocityMod();
		position.y = position.y + vel.y * getVelocityMod();
		if (position.x < 0)
		{
			vel.x = -vel.x;
			position.x = 0;
		}
		if (position.x > world.extension.x)
		{
			vel.x = -vel.x;
			position.x = world.extension.x;
		}
		if (position.y < 0)
		{
			vel.y = -vel.y;
			position.y = 0;
		}
		if (position.y > world.extension.y)
		{
			vel.y = -vel.y;
			position.y = world.extension.y;
		}
		// System.out.println(position);
	}

	// Velocity depend by energy
	public double getVelocityMod()
	{
		return 2;// energy/50;
	}

	public boolean isInRadius(Coordinate f)
	{
		if (position.squareDistance(f) < (radiusPeception() * radiusPeception()))
			return true;
		return false;
	}

	private double radiusPeception()
	{
		return Double.parseDouble(prop.getProperty(PERCEPTION_RADIUS));
	}

	public boolean isInRadiusMouth(Food f)
	{
		if (position.squareDistance(f.position) < (9))
			return true;
		return false;
	}

	public void eat(Food f)
	{
		energy = energy + f.energy;
	}

	@Override
	public String toString()
	{
		return "Liso [energy=" + energy + ", position=" + position + ", vel=" + vel + ", age=" + age + "]\n";
	}

	public int getX()
	{
		return (int) position.x;
	}

	public int gety()
	{
		return (int) position.y;
	}

	public boolean conception(Liso l2)
	{
		return conception(l2, 1.0);
	}

	// Prob is the prob of conception determianted by population of the world
	public boolean conception(Liso l2, double prob)
	{
		if (this.isInRadius(l2.position))
		{
			if (this.energy > 60 && l2.energy > 60)
			{
				if (Math.random() < (l2.fertility() * this.fertility()) * prob)
				{
					this.energy = this.energy / 2;
					return true;
				}
			}
		}
		return false;
	}

	private double fertility()
	{
		return Double.parseDouble(prop.getProperty(FERTILITY));
	}

	public boolean willEat(Food f)
	{
		if (this.energy > 400)// too full to eat
			return false;

		if (this.isInRadiusMouth(f))
		{
			return true;
		}
		return false;
	}

	public double getDailyEnergyConsume()
	{
		return Double.parseDouble(prop.getProperty(ENERGY_CONSUME));
	}

	public double getEnergy()
	{
		return energy;
	}

	public void setEnergy(double energy)
	{
		this.energy = energy;
	}

	public Coordinate getPosition()
	{
		return position;
	}

	public void setPosition(Coordinate position)
	{
		this.position = position;
	}

	public Coordinate getVel()
	{
		return vel;
	}

	public void setVel(Coordinate vel)
	{
		this.vel = vel;
	}

	public double getRadiusPeception()
	{

		return Double.parseDouble(prop.getProperty(PERCEPTION_RADIUS));
	}

	public double getPerception()
	{
		return Double.parseDouble(prop.getProperty(PERCEPTION));
	}

	public double getAge()
	{
		return age;
	}

	public void setAge(double age)
	{
		this.age = age;
	}

	public double getLisoLifeSpan()
	{
		return Double.parseDouble(prop.getProperty(LIFE_SPAN));
	}

	public double getFertility()
	{
		return Double.parseDouble(prop.getProperty(FERTILITY));
	}

	public void foodAwarness(World world)
	{
		// TODO Auto-generated method stub

	}
	

	public void setGeneration(int generation)
	{
		this.generation = generation;
	}

	public void setProp(Properties prop)
	{
		this.prop = prop;
	}

	public boolean isAware(Food f)
	{
		if (this.energy > 400)// too full to eat
			return false;

		if (this.isInRadius(f.position))
		{
			if (Math.random() < this.getPerception())
			{
				return true;
			}
		}
		return false;
	}

	public void moveTowardFood(ArrayList<Food> fArray)
	{
		// select closest food
		double d = Double.MAX_VALUE;
		Food bestFood = null;
		for (Food f : fArray)
		{
			double distance = position.squareDistance(f.position);
			if (distance < d)
			{
				d = distance;
				bestFood = f;
			}
		}
		if (bestFood == null)
			return;

		// redirect Liso velocity Vector
		double x = bestFood.position.x - this.position.x;
		double y = bestFood.position.y - this.position.y;
		double hySquare = Math.sqrt(x * x + y * y);
		x = x / hySquare;
		y = y / hySquare;

		vel.x = x;
		vel.y = y;
	}

	public Liso createNewLiso(World w, Liso l2)
	{
		int newGen = this.generation +l2.generation ;
		Properties babyProp = new Properties();
		for (Object keyObj : prop.keySet())
		{
			String key = (String) keyObj;
			String value = (String) prop.get(key);
			if (Math.random() > .5)
				value = (String) l2.prop.get(key);
			
			if (Math.random() < w.mutationProbability)
			{
				Double  valueMutated = Double.parseDouble(value) * (1 - (Math.random() - 0.5) / 100);
				//value = value * (1 - (Math.random() - 0.5) / 100);
				value=valueMutated+"";
				//System.out.println("\t\t\t\t\tMUTATION ! " + key + ":" + value);
			}
			babyProp.put(key, value);
		}
		
		return new Liso(w, getLisoLifeSpan(), newGen, babyProp);
	}
	
	public Properties getProp(){
		return prop;
	}

	public int getGeneration()
	{
		return generation;
	}

	public StringBuffer dumpToString()
	{
		StringBuffer sb = new StringBuffer("l;");
		sb.append(energy);
		sb.append(";");
		sb.append(position.x);
		sb.append(";");
		sb.append(position.y);
		sb.append(";");
		sb.append(vel.x);
		sb.append(";");
		sb.append(vel.y);
		sb.append(";");
		sb.append(age);
		sb.append(";");
		sb.append(generation);
		sb.append(";");
		
		for (Object key : prop.keySet())
		{
			sb.append((String)key);
			sb.append("=");
			sb.append(prop.getProperty((String) key));
			sb.append("#");
		}
		return sb;
	}

}
